package com.maiko.maikoaiagent.app;

import com.maiko.maikoaiagent.advisor.MyLoggerAdvisor;
import com.maiko.maikoaiagent.chatmemory.FileBasedChatMemory;
import com.maiko.maikoaiagent.rag.LoveAppRagCustomAdvisorFactory;
import com.maiko.maikoaiagent.rag.QueryRewriter;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;

import java.util.List;

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;

/**
 * @author: Maiko7
 * @create: 2025-06-06 14:11
 */
@Component
@Slf4j
public class LoveApp {

    // Spring AI提供的对话客户端，用于处理prompt和response的构建、调用和响应。
    private final ChatClient chatClient;

    private static final String SYSTEM_PROMPT = "扮演深耕恋爱心理领域的专家。开场向用户表明身份，告知用户可倾诉恋爱难题。" +
            "围绕单身、恋爱、已婚三种状态提问：单身状态询问社交圈拓展及追求心仪对象的困扰；" +
            "恋爱状态询问沟通、习惯差异引发的矛盾；已婚状态询问家庭责任与亲属关系处理的问题。" +
            "引导用户详述事情经过、对方反应及自身想法，以便给出专属解决方案。";

    /**
     * 初始化ChatClient
     * @param dashscopeChatModel
     */
    public LoveApp(ChatModel dashscopeChatModel) {
        // 初始化基于文件的对话记忆
        String fileDir = System.getProperty("user.dir") + "/chat-memory";
        ChatMemory chatMemory = new FileBasedChatMemory(fileDir);

        // 初始化基于内存的对话记忆
        // ChatMemory chatMemory = new InMemoryChatMemory();
        // dashscopeChatModel：模型实例（例如通义千问、GPT 等）；
        chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultSystem(SYSTEM_PROMPT)
                /**
                 * 配置默认 Advisor：
                 * MessageChatMemoryAdvisor：用于关联上下文记忆；
                 * SimpleLoggerAdvisor：自带的日志记录拦截器，记录请求和响应。
                 * MyLoggerAdvisor：自定义的日志记录拦截器，记录请求和响应。
                 */
                .defaultAdvisors(
                        new MessageChatMemoryAdvisor(chatMemory)
                        // new MessageChatMemoryAdvisor(chatMemory),
                        // new SimpleLoggerAdvisor()
                        // 自定义日志 Advisor，可按需开启
                        // new MyLoggerAdvisor()
                )
                .build();
    }

    /*
    * 初始化ChatClient，并使用数据库作为对话记忆。
    public LoveApp(ChatModel dashscopeChatModel, DatabaseChatMemory databaseChatMemory) {
        String memoryPath = System.getProperty("user.dir") + "/chat-memory";
        // ChatMemory chatMemory = new InMemoryChatMemory();
        // ChatMemory chatMemory = new FileBasedChatMemory(memoryPath);
        chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultSystem(SYSTEM_PROMPT)
                .defaultAdvisors(
                        new MessageChatMemoryAdvisor(databaseChatMemory),
                        new MyLoggerAdvisor()
                )
                .build();
    }
    */


    /**
     * 发起一次带有上下文记忆能力的 AI 对话请求。
     *
     * @param message 用户输入的消息内容
     * @param chatId 当前会话的唯一标识（用于记忆上下文），它这个就是比如你多个会话，你肯定每个会话要每个的标识不然还不混一起了
     * @return AI 模型生成的回复内容
     */
    public String doChat(String message, String chatId) {
        // 使用 chatClient 构建对话请求
        ChatResponse response = chatClient
                // 创建一个 prompt 对话会话入口，表示本次是新一轮对话
                .prompt()
                // 设置用户发送的文本消息（模型将以此作为输入）
                .user(message)
                /**
                 * 这两项是启用“上下文记忆系统”的关键配置，使用 MessageChatMemoryAdvisor。
                 * - param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)：指定当前对话的唯一 ID，AI 可以识别并记住该会话历史
                 * - param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)：从记忆中回忆最近 10 条上下文记录，提高连续对话能力 如果这里改成1的话，就没有不会记忆上下文了
                 */
                .advisors(spec -> spec
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)   // 设置当前对话的唯一标识
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))        // 设置记忆提取的历史消息数量
                // 发起对话调用，真正向底层大模型（如 DashScope 或 OpenAI）发送请求
                .call()
                // 获取完整的 ChatResponse 对象（包含模型生成的回复、元信息等）
                .chatResponse();

        // 从响应结果中提取出 AI 模型生成的文字内容（用于展示给用户）
        String content = response.getResult().getOutput().getText();

        // 打印日志以供调试或跟踪
        log.info("content: {}", content);

        // 返回 AI 生成的文本内容作为最终响应
        return content;
    }

    /**
     * LoveReport 是用于接收模型结构化输出的 Java 记录类。
     * 模型返回的 JSON 将会自动映射成这个类的实例。
     *
     * 示例 JSON 结构：
     * {
     *   "title": "小明的恋爱报告",
     *   "suggestions": [
     *     "多关心对方",
     *     "学会倾听",
     *     "保持沟通"
     *   ]
     * }
     *
     *
     * record 是一种特殊的类，自动生成以下内容：
     * 所有字段的 private final 声明
     * 构造方法
     * getter 方法（但方法名是字段名本身）
     * equals()、hashCode()、toString() 方法
     * 不可变性（字段不能被修改）
     *
     * 这是Java 14+引入的record（记录类）语法，是Java用于创建不可变的数据类的一种简洁方式。它非常适合用来表示“只包含数据”的结构，比如 DTO、VO、结构化响应模型等。
     *
     * record LoveReport(String title, List<String> suggestions) {} 这个就等价于下面这么多
     *
     * public final class LoveReport {
     *     private final String title;
     *     private final List<String> suggestions;
     *
     *     public LoveReport(String title, List<String> suggestions) {
     *         this.title = title;
     *         this.suggestions = suggestions;
     *     }
     *
     *     public String title() {
     *         return title;
     *     }
     *
     *     public List<String> suggestions() {
     *         return suggestions;
     *     }
     *
     *     @Override
     *     public boolean equals(Object o) { ... }
     *
     *     @Override
     *     public int hashCode() { ... }
     *
     *     @Override
     *     public String toString() {
     *         return "LoveReport[title=" + title + ", suggestions=" + suggestions + "]";
     *     }
     * }
     */
    record LoveReport(String title, List<String> suggestions) {
    }


    /**
     * 发起一次支持结构化输出的 AI 对话请求。
     * 模型将根据用户的消息内容，返回一份“恋爱建议报告”，并将其解析为 Java 对象 LoveReport。
     *
     * @param message 用户输入的内容（通常是感情问题、恋爱困扰等）
     * @param chatId  当前会话 ID（用于上下文记忆）
     * @return LoveReport 结构化恋爱建议报告
     */
    public LoveReport doChatWithReport(String message, String chatId) {
        // 使用 chatClient 构造对话请求
        LoveReport loveReport = chatClient
                // 初始化 prompt 构建器，开始新一轮对话
                .prompt()
                // 设置系统提示词，引导模型结构化输出报告。
                /**
                 * Q：为什么上面的doChat方法没有system这一个呢？
                 * A：因为你上面的doChat只是普通聊天，没有特别的行为要求，模型按默认习惯回答。
                 * 而doChatWithReport方法是有特定任务的，它是要结构化输出的，所以它需要特别指定系统提示词。
                 */
                .system(SYSTEM_PROMPT + "每次对话后都要生成恋爱结果，标题为{用户名}的恋爱报告，内容为建议列表")  // 标题为{用户名}的恋爱报告，内容为建议列表，它这个就是对应了上面的LoveReport，就是让Json转成LoveReport这个对象
                // 设置用户的输入内容
                .user(message)
                // 启用上下文记忆能力：指定对话 ID 和回忆历史轮数
                .advisors(spec -> spec
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)  // 对话 ID
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))       // 最多回忆 10 条历史记录
                // 执行调用，请求模型响应
                .call()
                // 将模型返回的结构化 JSON 响应直接解析为 Java 对象（LoveReport）
                .entity(LoveReport.class);

        // 打印结构化返回结果（便于日志追踪与调试）
        log.info("loveReport: {}", loveReport);

        // 返回结果对象给调用方
        return loveReport;
    }


    @Resource
    private VectorStore loveAppVectorStore;

    @Resource
    private Advisor loveAppRagCloudAdvisor;

    @Resource
    private VectorStore pgVectorVectorStore;

    @Resource
    private QueryRewriter queryRewriter;

    /**
     * 与RAG（检索增强生成）模型进行对话的方法
     * 该方法通过集成外部知识库和历史对话信息来生成回复，旨在提供更加智能和个性化的对话体验
     *
     * 这说一大堆其实就是RAG工作流程的第三步：查询增强和关联。
     *
     * @param message 用户输入的消息，用于生成回复的依据
     * @param chatId 对话ID，用于检索和存储对话历史
     * @return String 返回经过RAG模型处理后生成的回复文本
     */
    public String doChatWithRag(String message, String chatId) {
        // 查询重写
        String rewrittenMessage = queryRewriter.doQueryRewrite(message);

        // 构建ChatResponse对象，用于获取模型的回复
        ChatResponse chatResponse = chatClient
                .prompt() // 开始一个新的提示或对话
                //.user(message) // 设置用户输入的消息
                .user(rewrittenMessage) // 使用改写后的查询
                .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                // 开启日志，便于观察效果
                //.advisors(new MyLoggerAdvisor())
                // 应用RAG知识库问答 （你看讲一大堆RAG，其实和上面的doChat方法相比就多了这一行代码，这就是查询增强）
                //.advisors(new QuestionAnswerAdvisor(loveAppVectorStore))

                // 将自定义的 RAG 检索增强顾问添加到配置中
                .advisors(
                        LoveAppRagCustomAdvisorFactory.createLoveAppRagCustomAdvisor(loveAppVectorStore, "单身")
                )
                // 应用RAG检索增强服务（基于云知识库服务）
                //.advisors(loveAppRagCloudAdvisor)

                // 应用RAG检索增强服务（基于pgVector）
                //.advisors(new QuestionAnswerAdvisor(pgVectorVectorStore))
                .call()
                .chatResponse();
        // 提取回复内容
        String content = chatResponse.getResult().getOutput().getText();
        // 记录日志，查看生成的回复内容
        log.info("content: {}", content);
        // 返回生成的回复文本
        return content;
    }


    /**
     * 使用注入的工具数组 allTools 调用 ChatClient 进行带工具辅助的对话交互。
     */
    @Resource
    private ToolCallback[] allTools;

    /**
     * 基于传入的用户消息和聊天会话 ID，调用 ChatClient 发送请求，
     * 并结合工具集执行辅助操作，返回聊天结果文本。
     *
     * @param message 用户输入的聊天消息
     * @param chatId  聊天会话的唯一标识，用于聊天记忆检索
     * @return AI 回复的文本内容
     */
    public String doChatWithTools(String message, String chatId) {
        ChatResponse response = chatClient
                .prompt()
                .user(message) // 设置用户消息
                .advisors(spec -> spec
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) // 关联会话ID，实现上下文记忆
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))      // 设置检索历史消息条数
                .advisors(new MyLoggerAdvisor()) // 自定义日志顾问，便于调试和监控
                .tools(allTools)                 // 注入已注册的工具数组，支持工具调用
                .call()                         // 发送请求并同步等待结果
                .chatResponse();                // 获取聊天响应对象

        String content = response.getResult().getOutput().getText();

        log.info("content: {}", content); // 打印返回结果日志

        return content;
    }

    /**
     *   通过自动注入ToolCallbackProvider获取配置中定义的MCP服务提供的所有工具，并提供给ChatClient
     *   它这个就是，只要你去resources下面中的mcp-servers.json中配置了，就有工具了
     */
    @Resource
    private ToolCallbackProvider toolCallbackProvider; // 注入工具回调提供者，用于注册 AI 可调用的工具方法

    /**
     * 使用工具增强的 chatClient 发起一次对话请求，并返回 AI 回复内容
     *
     * @param message 用户输入的消息
     * @param chatId  当前对话的唯一标识，用于记忆上下文
     * @return AI 回复的内容
     */
    public String doChatWithMcp(String message, String chatId) {
        // 构建一次聊天请求
        ChatResponse response = chatClient
                .prompt() // 创建一个新的 prompt 对话上下文
                .user(message) // 添加用户输入的消息
                .advisors(spec -> spec
                        // 设置对话记忆相关参数：使用 chatId 标识会话，最多回顾10条历史消息
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                .advisors(new MyLoggerAdvisor()) // 添加自定义日志记录器（用于调试或打印 Prompt 等）
                .tools(toolCallbackProvider) // 注册当前对话中允许使用的工具方法
                .call() // 发起请求，调用大模型
                .chatResponse(); // 获取对话响应对象

        // 提取 AI 回复的文本内容
        String content = response.getResult().getOutput().getText();
        log.info("content: {}", content); // 记录返回内容日志
        return content; // 返回 AI 的文本回复
    }






}
